home *** CD-ROM | disk | FTP | other *** search
- /* $Header: dfile.c,v 2.7 90/03/06 12:21:12 chip Exp $
- *
- * Filter destination(s) through delivery file(s).
- *
- * $Log: dfile.c,v $
- * Revision 2.7 90/03/06 12:21:12 chip
- * Move logging into log.c and address parsing into addr.c.
- * New: error delivery file for messages that fail.
- * Major rearrangement of delivery file code.
- *
- * Revision 2.6 90/02/23 16:35:54 chip
- * \Fix problems determining legality of user references.
- *
- * Revision 2.5 90/02/23 14:16:44 chip
- * Support "#!" in delivery files.
- * Support "user|program" and "user?error" from delivery files.
- * Improve debugging and error message formatting.
- * Rearrange code for clarity.
- *
- * Revision 2.4 89/11/10 12:23:52 network
- * Be more selective about trying to deliver to MBX_UNDEL.
- *
- * Revision 2.3 89/11/01 12:31:11 network
- * Use the new exists() function.
- *
- * Revision 2.2 89/09/29 18:17:59 network
- * Save message when delivery file produces no output,
- * unless delivery file output the "DROP" string.
- * Don't recopy temp files for sys and post-user delfiles.
- *
- * Revision 2.1 89/06/09 12:25:24 network
- * Update RCS revisions.
- *
- * Revision 1.10 89/06/09 12:23:49 network
- * Baseline for 2.0 release.
- *
- */
-
- #include "deliver.h"
- #include <sys/stat.h>
-
- /*----------------------------------------------------------------------
- * Filter all valid destinations through the system delivery file.
- * Return 1 (executed), 0 (not executed), -1 (no attempt made).
- */
-
- static int sys_ac;
- static char **sys_av;
-
- char **
- sys_args(pac)
- int *pac;
- {
- char **fav;
- int fac, a;
-
- fav = (char **) zalloc(sys_ac * sizeof(char **));
- fac = 0;
-
- for (a = 0; a < sys_ac; ++a)
- {
- char *addr;
-
- addr = sys_av[a];
-
- /* Note invalid address(es); report them later. */
-
- if (!addr_clean(addr))
- (void) dest(addr, CL_USER, (char *) NULL);
-
- /* Note non-user address(es); report them later. */
-
- else if (addr_class(addr) != CL_USER)
- (void) addr_dest(addr, (CONTEXT *)NULL);
-
- /* Let the system delivery file handle the rest. */
-
- else
- fav[fac++] = addr;
- }
-
- /*
- * If there were any good names found, let loose the delivery
- * file. Note the meaning of "good" is "well-formed", not "valid".
- * Thus the system delivery file has control over the handling of
- * all local deliveries, not just those to valid users.
- */
-
- if (fac <= 0)
- {
- free((char *) fav);
- fav = NULL;
- }
-
- *pac = fac;
- return fav;
- }
-
- sys_dfile(ac, av)
- int ac;
- char **av;
- {
- sys_ac = ac;
- sys_av = av;
- return glob_dfile("system", sys_deliver, sys_args, FALSE);
- }
-
- /*----------------------------------------------------------------------
- * Filter some undelivered destinations through the post-user
- * delivery file.
- * Return 1 (executed), 0 (not executed), -1 (no attempt made).
- */
-
- post_choose(d)
- DEST *d;
- {
- if ((d->d_class == CL_USER || d->d_class == CL_UUCP)
- && (d->d_state == ST_WORKING
- || (d->d_state == ST_ERROR && d->d_error == E_NSUSER)))
- {
- d->d_state = ST_HOLD;
- return TRUE;
- }
-
- return FALSE;
- }
-
- char **
- post_args(pac)
- int *pac;
- {
- return choose_args(pac, post_choose);
- }
-
- post_dfile()
- {
- return glob_dfile("post-user", post_deliver, post_args, FALSE);
- }
-
- /*----------------------------------------------------------------------
- * Filter broken (but well-formed) destinations through the error
- * delivery file.
- * Return 1 (executed), 0 (not executed), -1 (no attempt made).
- */
-
- err_choose(d)
- DEST *d;
- {
- return (d->d_state == ST_ERROR);
- }
-
- char **
- err_args(pac)
- int *pac;
- {
- return choose_args(pac, err_choose);
- }
-
- err_dfile()
- {
- return glob_dfile("error", err_deliver, err_args, TRUE);
- }
-
- /*----------------------------------------------------------------------
- * Execute a global delivery file given description of delivery file,
- * its path, a function to get arguments, and a boolean to indicate whether
- * a lack of output is a normal condition.
- * Return 1 (executed), 0 (not executed), -1 (no attempt made).
- */
-
- glob_dfile(desc, dfile, args, silent_ok)
- char *desc;
- char *dfile;
- char **(*args)();
- int silent_ok;
- {
- char **fav;
- int fac;
-
- /*
- * If the delivery file is missing, forget it.
- */
-
- if (!exists(relpath(eff_ct->ct_home, dfile)))
- {
- if (verbose)
- message("no %s delivery file\n", desc);
- return -1;
- }
-
- /*
- * If we've been asked not to run delivery files, forget it.
- */
-
- if (!rundfiles)
- {
- if (verbose)
- message("%s delivery file disabled\n", desc);
- return -1;
- }
-
- /*
- * Now we can get the argument list.
- * If the list is empty, that's all she wrote.
- */
-
- if ((fav = (*args)(&fac)) == NULL)
- return 0;
-
- /*
- * "Just do it."
- * If we get nothing back from the delivery file,
- * and if silence is not supposed to be permitted,
- * put the message in the "undelivered" mailbox.
- */
-
- if (run_dfile(eff_ct, dfile, fac, fav, (DEST *)NULL) <= 0
- && !silent_ok)
- {
- if (verbose)
- message("%s delivery file: no output\n", desc);
- dest_undel(eff_ct->ct_name);
- }
-
- /*
- * The argument function allocated the arguments; we free them.
- */
-
- free((char *) fav);
-
- /*
- * Return "done".
- */
-
- return 1;
- }
-
- /*----------------------------------------------------------------------
- * Generate a delivery file argument list.
- * We do this by getting an array of all destinations,
- * then keeping only the ones we want.
- */
-
- char **
- choose_args(pac, choose)
- int *pac;
- int (*choose)();
- {
- DEST *d;
- char **fav;
- int count, fac;
-
- if ((count = dest_count()) <= 0)
- return NULL;
-
- fav = (char **) zalloc(count * sizeof(char *));
- fac = 0;
-
- for (d = first_dest(); d; d = next_dest(d))
- {
- if ((*choose)(d))
- fav[fac++] = d->d_name;
- }
-
- if (fac <= 0)
- {
- free((char *) fav);
- fav = NULL;
- }
-
- *pac = fac;
- return fav;
- }
-
- /*----------------------------------------------------------------------
- * Filter all user destinations through their local delivery files.
- * Return 1 (some executed), 0 (none executed), -1 (no attempt made).
- */
-
- user_dfiles()
- {
- DEST *d;
- int nfound, ret;
-
- /*
- * If we've been asked not to run delivery files, forget it.
- */
-
- if (!rundfiles)
- {
- if (verbose)
- message("user delivery files disabled\n");
- return -1;
- }
-
- /*
- * Continue to loop through all addresses until no destination
- * that needs expanding can be found.
- */
-
- ret = 0;
- do {
- nfound = 0;
- for (d = first_dest(); d; d = next_dest(d))
- {
- if (d->d_class == CL_USER
- && d->d_state == ST_WORKING
- && !d->d_dfdone)
- {
- u_dfile(d);
- d->d_dfdone = TRUE;
- ret = 1;
- }
- }
- } while (nfound > 0);
-
- return ret;
- }
-
- /*----------------------------------------------------------------------
- * Run the user delivery file (if any) for the specified destination.
- */
-
- u_dfile(d)
- DEST *d;
- {
- struct stat st;
- CONTEXT *ct;
- char *s, *udel_path;
- int n;
-
- if ((ct = name_context(d->d_name)) == NULL)
- {
- dest_err(d, E_CTLOST);
- return;
- }
-
- /*
- * If user's home directory is missing, forget it.
- * If user's home directory is writable to the world,
- * executing the delivery file would allow a security breach!
- * Thanks to Jon Zeeff for this hint...
- */
-
- if (stat(ct->ct_home, &st) == -1
- || (st.st_mode & S_IFMT) != S_IFDIR)
- {
- if (verbose)
- message("user %s: home directory %s is missing!\n",
- ct->ct_name, ct->ct_home);
- return;
- }
-
- if (st.st_mode & 02)
- {
- if (verbose)
- message("user %s: home directory is writable to the world!\n",
- ct->ct_name);
- return;
- }
-
- /*
- * If there is no delivery file to execute, just return.
- */
-
- s = relpath(ct->ct_home, user_deliver);
- if (!exists(s))
- {
- if (verbose)
- message("%s has no delivery file\n", d->d_name);
- return;
- }
- udel_path = copystr(s);
-
- /*
- * Put this destination on hold.
- * It will be ignored unless it's named by a delivery file.
- */
-
- d->d_state = ST_HOLD;
-
- /*
- * Time to run the file!
- * If we get nothing back from the user delivery file,
- * put the message in the user's "undelivered" mailbox--
- * or ours, depending on the kind of failure.
- */
-
- n = run_dfile(ct, udel_path, 1, &d->d_name, d);
- if (n <= 0)
- {
- if (verbose)
- message("u_dfile: no output\n");
- dest_undel((n == 0 ? ct : eff_ct)->ct_name);
- }
-
- free(udel_path);
- }
-
- /*----------------------------------------------------------------------
- * Execute a delivery file (global or user).
- * Return the count of valid destinations we got back from it.
- * If delivering to MBX_UNDEL is possible, errors return zero.
- * Otherwise, errors return -1.
- */
-
- int
- run_dfile(ct, dfile, fac, fav, d)
- CONTEXT *ct;
- char *dfile;
- int fac;
- char **fav;
- DEST *d;
- {
- FILE *fp;
- char **av;
- int ac, a, fd, linecount;
- static char buf[BUFSIZ];
-
- if (!ct)
- return -1;
-
- if (! ok_context(eff_uid, real_uid, real_gid, ct))
- {
- if (d)
- dest_err(d, E_CTPERM);
- else
- message("No permissions to run as %s\n", ct->ct_name);
-
- return -1;
- }
-
- /*
- * We trust the superuser not to stomp on the temp files.
- * Other users get copies of the temp files.
- */
-
- if (ct->ct_uid == 0)
- {
- /* We trust the superuser; don't bother copying again */
-
- if (dont_copy() < 0)
- return -1;
- }
- else
- {
- /* Copy the temp files again */
-
- if (copy_again() < 0)
- return -1;
-
- /* Allow the given user to own and read the copies */
-
- if (give_temps(ct) < 0)
- return -1;
- }
-
- /* Produce the arguments in the form we need. */
-
- av = (char **) zalloc((fac + 4) * sizeof(char *));
- ac = 0;
-
- /* Process the "#!" hack. */
-
- if ((fd = open(dfile, O_RDONLY)) != -1)
- {
- char *p;
- int rd;
- static char hashbang[64]; /* arbitrary */
-
- rd = read(fd, hashbang, sizeof(hashbang) - 1);
- (void) close(fd);
- hashbang[rd > 0 ? rd : 0] = 0;
-
- if ((p = strchr(hashbang, '\n')) != NULL
- && hashbang[0] == '#'
- && hashbang[1] == '!')
- {
- *p = 0;
-
- /* Interpreter. */
-
- p = hashbang + 2;
- while (isspace(*p))
- ++p;
- av[ac++] = p;
- while (*p && !isspace(*p))
- ++p;
- if (*p)
- *p++ = 0;
-
- /* Only one argument; sorry. */
-
- while (isspace(*p))
- ++p;
- if (*p)
- av[ac++] = p;
- }
- }
-
- /*
- * If no "#!" found, use the default shell.
- * Then add the delivery file, address(es), and a NULL.
- */
-
- if (ac == 0)
- av[ac++] = shell;
- av[ac++] = dfile;
- for (a = 0; a < fac; ++a)
- av[ac++] = fav[a];
- av[ac] = NULL;
-
- /* Here we go! */
-
- if (verbose)
- message("Processing delivery file as %s\n", ct->ct_name);
-
- fp = ct_fopenv(ct, av[0], av, "r");
-
- /* We don't need the argument vector any more. */
-
- free((char *) av);
-
- /* If something went wrong, bail out now. */
-
- if (fp == NULL)
- {
- error("can't execute delivery file as %s\n", ct->ct_name);
- return 0;
- }
-
- /*
- * Read the standard output of the delivery file.
- */
-
- linecount = 0;
-
- while (fgets(buf, GETSIZE(buf), fp) != NULL)
- {
- DEST *nd;
- char *p;
-
- if ((p = strchr(buf, '\n')) != NULL)
- *p = 0;
- else
- {
- int c;
-
- while ((c = fgetc(fp)) != '\n' && c != EOF)
- ; /* keep reading */
-
- error("invalid line from delivery file: '%s'\n", buf);
- continue;
- }
-
- /* Debugging message: display input line. */
-
- if (verbose)
- message("\t'%s'\n", buf);
-
- /* Okay-to-drop directive is a special case. */
-
- if (strcmp(buf, DFILE_DROP) == 0)
- {
- message("\tDelivery file says OK to drop\n");
- ++linecount;
- continue;
- }
-
- /* Parse destination; if none, look for next line. */
-
- if ((nd = addr_dest(buf, ct)) == NULL)
- continue;
-
- /* We got some output; remember that. */
-
- ++linecount;
-
- /* If destination was on hold, it's not so anymore. */
-
- if (nd->d_state == ST_HOLD)
- nd->d_state = ST_WORKING;
- }
-
- (void) ct_fclose(fp);
-
- return linecount;
- }
-
- /*----------------------------------------------------------------------
- * Make the temp files readable in the given context.
- * This is needed because the temps are readable by owner only.
- */
-
- int
- give_temps(ct)
- CONTEXT *ct;
- {
- int err, t;
-
- if (!ct)
- return -1;
-
- err = 0;
- for (t = T_HDRCOPY; t <= T_BODYCOPY; ++t)
- {
- if (chmod(tfile[t], 0600) == -1)
- {
- syserr("can't chmod %s", tfile[t]);
- ++err;
- }
- if (chown(tfile[t], ct->ct_uid, ct->ct_gid) == -1)
- {
- syserr("can't chown %s to %d/%d",
- tfile[t], ct->ct_uid, ct->ct_gid);
- ++err;
- }
- }
-
- return err ? -1 : 0;
- }
-